//
// Created by martin on 4/28/22.
//

#include "Configurer.h"
#include "muduo/base/Logging.h"

#include <memory>
#include <locale>
#include <algorithm>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>

using namespace ftp;

namespace
{

/**
 * trim string line, delete leading space char and tail space char.
 * space char is defined by isspace(), including:
 * ' '(0x20) '\t'(0x09) '\n'(0x0a) '\v'(0x0b) '\f'(0x0c) '\r'(0x0d)
 */
void str_trimLine(char* line)
{
	if (!line) return;
	size_t size = strlen(line);

	int i = 0, j = static_cast<int>(size - 1);
	while (j >= 0 && isspace(line[j])) j--;
	while (i < static_cast<int>(size) && isspace(line[i])) i++;

	int k = 0;
	while (i <= j) {
		line[k++] = line[i++];
	}
	line[k] = '\0';
}

/**
 * trim string s, delete leading SP and tail SP.
 * SP is ' '(0x20).
 */
void trim(std::string& s)
{
	if (s.empty()) return;

	size_t i = 0;
	while (i < s.size() && ' ' == s[i]) {
		++i;
	}
	s.erase(0, i);

	int j = static_cast<int>(s.size() - 1);
	while (j >= 0 && ' ' == s[static_cast<size_t>(j)]) {
		--j;
	}
	s.erase(static_cast<size_t>(j + 1), s.size() - static_cast<size_t>(j) - 1);
}

} // end namespace

bool Configurer::load(const std::string& filepath)
{
	assert(loaded_ == false);
	std::shared_ptr<FILE> fileGuard(::fopen(filepath.c_str(), "r"), ::fclose);
	if (!fileGuard) {
		LOG_SYSFATAL << "Configurer::load file " << filepath << " error";
		return false;
	}

	char line[kMaxNumPerLine + 1]; // +1 for NUL byte
	while (!feof(fileGuard.get())) {
		if (!fgets(line, sizeof(line), fileGuard.get())) // error
			continue;
		if (line[0] == 0) // ignore null line
			continue;
		// ignore comment line
		if (line[0] == ';' || line[0] == ' ' || line[0] == '#' || line[0] == '\t')
			continue;
		// ignore <LF>, <CRLF> line without valid content
		if (line[0] == '\n' || (line[0] == '\r' && line[1] == '\n'))
			continue;

		// trim space char in front and tail
		str_trimLine(line);

		// check trim result, ignore invalid line
		if (line[0] == 0 || line[0] == '#' || line[0] == '[')
			continue;

		// item = value, trim <SP> around "="
		char* needlPos = strchr(line, '=');
		if (needlPos) {
			Item* item = new Item;
			std::string name(line, static_cast<size_t>(needlPos - line));
			std::string value(needlPos + 1, static_cast<size_t>(&line[strlen(line) - 1] - needlPos + 1));
			trim(name);
			trim(value);
			item->name_ = name;
			item->value_ = value;
			items_.push_back(item);
		}
	}

	loaded_ = true;
	return true;
}

std::string Configurer::getItemString(const std::string& itemname, const std::string& defaultString)
{
	if (itemname.empty()) return itemname;
	auto it = std::find_if(items_.begin(), items_.end(), [&itemname](const Item* item)->bool {
		return (item && strcasecmp(item->name_.c_str(), itemname.c_str()) == 0);
		});
	if (it != items_.end()) {
		return (*it)->value_;
	}
	return defaultString;
}

int Configurer::getItemInt(const std::string& itemname, int defaultValue)
{
	int res = defaultValue;
	std::string valueString = getItemString(itemname);
	if (!valueString.empty()) {
		res = atoi(valueString.c_str());
	}
	return res;
}

void Configurer::checkRootPath()
{
	assert(loaded_ == true);
	const std::string& rootPath = getItemString("FtpRootDirectory", kDefaultFtpRootPath);
	if (::access(rootPath.c_str(), F_OK) < 0) { // file not exist
		if (::mkdir(rootPath.c_str(), 0771) < 0) { // create ftp root directory if not exist
			LOG_SYSFATAL << "mkdir root path: " << rootPath << " error";
		}

		if (::chmod(rootPath.c_str(), 0771) < 0) {
			LOG_SYSFATAL << "chmod root path: " << rootPath << " error";
		}
	}

	if (::access(rootPath.c_str(), R_OK) < 0) {
		LOG_SYSFATAL << "read Ftp Root Directory " << rootPath << " error";
	}

	if (::access(rootPath.c_str(), W_OK) < 0) {
		LOG_SYSFATAL << "write Ftp Root Directory " << rootPath << " error";
	}

}
